﻿using Microsoft.Azure;
using Microsoft.Cloud.API.Azure.Models;
using Microsoft.WindowsAzure.Management.Compute;
using Microsoft.WindowsAzure.Management.Compute.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Microsoft.Cloud.API.Azure.Services
{
    public class ComputeService : IDisposable
    {
        private readonly ComputeManagementClient computeManagementClient = null;

        public ComputeService(string subscriptionId)
        {
            this.computeManagementClient = new ComputeManagementClient(ADALService.GetCredential(subscriptionId), ADALService.ServiceManagementUrl);
        }

        public ComputeService(SubscriptionCloudCredentials credentials, Uri baseUri)
        {
            this.computeManagementClient = new ComputeManagementClient(credentials, baseUri);
        }

        /// <summary>
        /// create cloud service
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public async Task<AzureOperationResponse> CreateCloudServiceAsync(HostedServiceCreateParameters parameters)
        {
            // Create the hosted service
            var response = await this.computeManagementClient.HostedServices.CreateAsync(parameters);

            // A successful operation returns status code 201 (Created).
            return response;
        }

        /// <summary>
        /// list all cloud services
        /// </summary>
        /// <returns></returns>
        public async Task<IList<HostedServiceListResponse.HostedService>> GetCloudServiceListAsync()
        {
            var response = await this.computeManagementClient.HostedServices.ListAsync();

            return response.HostedServices;
        }

        /// <summary>
        /// check if cloud service is available
        /// </summary>
        /// <param name="serviceName"></param>
        /// <returns></returns>
        public async Task<HostedServiceCheckNameAvailabilityResponse> CheckCloudServiceNameAsync(string serviceName)
        {
            var response = await this.computeManagementClient.HostedServices.CheckNameAvailabilityAsync(serviceName);

            return response;
        }

        /// <summary>
        /// create vm deployment
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<OperationStatusResponse> CreateVMDeploymentAsync(VMDeploymentCreationModel model)
        {
            // The Deployment where the Virtual Machine will be hosted
            var parameters = new VirtualMachineCreateDeploymentParameters
            {
                DeploymentSlot = DeploymentSlot.Production,
                Name = model.CloudServiceName,
                Label = model.CloudServiceName
            };

            #region VM Role
            // The Role defines the details of the VM parameters
            var vmRole = new Role
            {
                OSVirtualHardDisk = this.CreateOSVHD(model),
                // You could use DataVirtualHardDisks here to add additional persistent data disks
                RoleName = model.VMName,
                RoleType = VirtualMachineRoleType.PersistentVMRole.ToString(),
                RoleSize = model.VMSize,
                ProvisionGuestAgent = true,
                AvailabilitySetName = model.AvailabilitySetName,
                //DataVirtualHardDisks = null
            };

            // Add ConfigurationSets
            vmRole.ConfigurationSets = this.CreateConfigurationSet(model);
            #endregion

            parameters.Roles.Add(vmRole);

            var response = await this.computeManagementClient.VirtualMachines.CreateDeploymentAsync(model.CloudServiceName, parameters);

            return response;
        }

        /// <summary>
        /// list all virtual machines
        /// </summary>
        /// <returns></returns>
        public async Task<IList<VirtualMachineDiskListResponse.VirtualMachineDisk>> GetVMDiskListAsync()
        {
            var response = await this.computeManagementClient.VirtualMachineDisks.ListDisksAsync();

            return response.Disks;
        }

        /// <summary>
        /// add vm role
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<OperationStatusResponse> CreateVMRoleAsync(VMCreationModel model)
        {
            var parameters = new VirtualMachineCreateParameters
            {
                RoleName = model.VMName,
                RoleSize = model.VMSize,
                OSVirtualHardDisk = this.CreateOSVHD(model),
                ConfigurationSets = this.CreateConfigurationSet(model),
                ProvisionGuestAgent = true,
                AvailabilitySetName = model.AvailabilitySetName,
                //DataVirtualHardDisks = null
            };

            var response = await this.computeManagementClient.VirtualMachines.CreateAsync(model.CloudServiceName, model.CloudServiceName, parameters);

            return response;
        }

        /// <summary>
        /// shutdown vm role
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<OperationStatusResponse> ShutdownVMRoleAsync(VMOperationModel model)
        {
            var parameters = new VirtualMachineShutdownParameters()
            {
                PostShutdownAction = PostShutdownAction.StoppedDeallocated,
            };

            return await this.computeManagementClient.VirtualMachines.ShutdownAsync(model.CloudServiceName, model.DeploymentName, model.VMName, parameters);
        }

        /// <summary>
        /// start vm role
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<OperationStatusResponse> StartVMRoleAsync(VMOperationModel model)
        {
            return await this.computeManagementClient.VirtualMachines.StartAsync(model.CloudServiceName, model.DeploymentName, model.VMName);
        }

        /// <summary>
        /// restart vm role
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<OperationStatusResponse> RestartVMRoleAsync(VMOperationModel model)
        {
            return await this.computeManagementClient.VirtualMachines.RestartAsync(model.CloudServiceName, model.DeploymentName, model.VMName);
        }

        /// <summary>
        /// get virtual machine
        /// </summary>
        /// <param name="cloudServiceName"></param>
        /// <param name="deploymentName"></param>
        /// <param name="vmName"></param>
        /// <returns></returns>
        public async Task<VirtualMachineGetResponse> GetVMRoleAsync(string cloudServiceName, string deploymentName, string vmName)
        {
            return await this.computeManagementClient.VirtualMachines.GetAsync(cloudServiceName, deploymentName, vmName);
        }

        /// <summary>
        /// get os images
        /// </summary>
        /// <returns></returns>
        public async Task<IList<VirtualMachineOSImageListResponse.VirtualMachineOSImage>> GetOSImagesListAsync()
        {
            var response = await this.computeManagementClient.VirtualMachineOSImages.ListAsync();

            return response.Images;
        }

        /// <summary>
        /// get deployment info
        /// </summary>
        /// <param name="cloudServiceName"></param>
        /// <param name="deploymentName"></param>
        /// <returns></returns>
        public async Task<DeploymentGetResponse> GetDeploymentAsync(string cloudServiceName, string deploymentName)
        {
            return await this.computeManagementClient.Deployments.GetByNameAsync(cloudServiceName, deploymentName);
        }

        /// <summary>
        /// get rdp file
        /// </summary>
        /// <param name="cloudServiceName"></param>
        /// <param name="deploymentName"></param>
        /// <param name="vmName"></param>
        /// <returns></returns>
        public async Task<VirtualMachineGetRemoteDesktopFileResponse> GetRDPFileAsync(string cloudServiceName, string deploymentName, string vmName)
        {
            return await this.computeManagementClient.VirtualMachines.GetRemoteDesktopFileAsync(cloudServiceName, deploymentName, vmName);
        }

        public void Dispose()
        {
            if (this.computeManagementClient != null)
            {
                this.computeManagementClient.Dispose();
            }
        }

        private OSVirtualHardDisk CreateOSVHD(VMCreationModel vm)
        {
            return new OSVirtualHardDisk
            {
                HostCaching = VirtualHardDiskHostCaching.ReadWrite,
                MediaLink = new Uri(string.Format("https://{0}.blob.{2}/vhds/{1}", vm.StorageAccountName, vm.VMName, ADALService.StorageDomainSuffix)),
                SourceImageName = vm.ImageName
            };
        }

        private IList<ConfigurationSet> CreateConfigurationSet(VMCreationModel vm)
        {
            var result = new List<ConfigurationSet>();

            // Add a ConfigurationSet for the Windows specific configuration
            result.Add(new ConfigurationSet
            {
                ConfigurationSetType = ConfigurationSetTypes.WindowsProvisioningConfiguration,
                ComputerName = vm.VMName,
                AdminUserName = vm.VMName + "_" + vm.AdminUserName,
                AdminPassword = vm.AdminPassword,
                EnableAutomaticUpdates = false
            });

            // Add a ConfigurationSet describing the ports to open to the outside
            result.Add(new ConfigurationSet
            {
                ConfigurationSetType = ConfigurationSetTypes.NetworkConfiguration,
                InputEndpoints = new List<InputEndpoint>()
                {
                    new InputEndpoint()
                    {
                        Name = "Remote Desktop",
                        Protocol = InputEndpointTransportProtocol.Tcp,
                        LocalPort = 3389 
                    },
                    new InputEndpoint()
                    {
                        Name = "PowerShell",
                        Protocol = InputEndpointTransportProtocol.Tcp,
                        LocalPort = 5986
                    }
                }
            });

            return result;
        }
    }
}